/** @file
 * Sets up SDL for drawing pixels to the screen and provides wrapper functions
 * for drawing cell objects and representing their colors. 
 */
#include <stdlib.h>
#include <stdio.h>

#include "sdlio.h"
#include "cellvm.h"

duptr display_call;

/**
 * Updates a pixel on the screen to the color specified.
 * @param screen SDL screen to draw to.
 * @param x Horizontal coord. 
 * @param y Vertical coord.
 * @param R Red color value. (16 bit)
 * @param G Green color value.
 * @param B Blue color value.
 * @param flush Update the display now if true.
 *
inline static void display_update(SDL_Surface *screen, int x, int y, int R, int G, int B, int flush) {
	int color = SDL_MapRGB(screen->format, R, G, B);

	((uint16_t *)screen->pixels)[x + (y * screen->pitch)/2] = color;

	if(flush)
		SDL_UpdateRect(screen,0, 0, X, Y);
}*/


inline static void display_update(SDL_Surface *screen, int x, int y, int R, int G, int B, int flush) {
	SDL_Rect pixel;
	int color = SDL_MapRGB(screen->format, R, G, B);

	pixel.x = x * PIXELSPEROBJECT;
	pixel.y = y * PIXELSPEROBJECT;
	pixel.w = PIXELSPEROBJECT;
	pixel.h = PIXELSPEROBJECT;

	SDL_FillRect(screen, &pixel, color);
	if(flush)
		SDL_UpdateRect(screen, 0, 0, X*PIXELSPEROBJECT, Y*PIXELSPEROBJECT);
}

int sdl_input(SDL_Surface *screen, struct cell_cluster *cluster) {
	SDL_Event event;
	
	while(SDL_PollEvent(&event)) {
		switch(event.type) {
			case SDL_QUIT:
				exit(0);
			/* Catch some key presses. */
			case SDL_KEYDOWN:
				switch(event.key.keysym.sym) {
					case SDLK_RETURN:
						return SDLK_RETURN;
					/* Generation view. */
					case SDLK_g:
						printf("Display generation request\n");
						draw_all(screen, cluster, DRAW_GENERATION);
						display_call = draw_local_generation;
						return SDLK_g;
					/* Energy view. */
					case SDLK_e:
						printf("Display energy request\n");
						draw_all(screen, cluster, DRAW_ENERGY);
						display_call = draw_local_energy;
 						return SDLK_e;
					/* Living view. */
					case SDLK_l:
						printf("Display living request\n");
						draw_all(screen, cluster, DRAW_LIVING);
						display_call = draw_local_living;
						return SDLK_l;
					case SDLK_q:
						exit(0);
					default:
						break;
				}
			default:
				break;
		}
	}
	return 0;
}

SDL_Surface *display_init(void) {
	SDL_Surface *screen;	

	/* SDL library init. */
	if(SDL_Init(SDL_INIT_VIDEO) < 0) {
		printf("Video init error %s\n", SDL_GetError());
		return NULL;
	}
	
	/* Create a screen. */
	if(!(screen = SDL_SetVideoMode(X*PIXELSPEROBJECT, Y*PIXELSPEROBJECT, 16, SDL_SWSURFACE))) {
		printf("Video mode set error %s\n", SDL_GetError());
		SDL_Quit();
		return NULL;
	}

	SDL_WM_SetCaption("Silicon Genesis", "Silicon Genesis");
	display_call = draw_local_living; /* Set display call to generations by default. */

#ifdef DEBUG
	printf("Display init ok: %dx%d @ %d^2\n", X*PIXELSPEROBJECT, Y*PIXELSPEROBJECT, PIXELSPEROBJECT);
#endif
	return screen;
}

void display_close(void) {
	SDL_Quit();
#ifdef DEBUG
	printf("Display closed\n");
#endif
}

int display_clear(SDL_Surface *screen) {
	int x, y;

	/* Iterate thru the x and y coords and blank them. */
	for(x = 0; x < X; x++)
		for(y = 0; y < Y; y++)
			display_update(screen, x, y, 0, 0, 0, 0);

	display_update(screen, x, y, 0, 0, 0, 1);		
	return 0;
}

int display_title(SDL_Surface *screen) {
	SDL_Surface *bitmap, *converted;
	SDL_Rect update;
	
	if(!(bitmap = SDL_LoadBMP(STARTLOGO))) {
#ifdef DEBUG
		printf("logo load error\n");
#endif
		return -1;
	}
	
	/* Convert loaded bitmap into our display format. */
	converted = SDL_DisplayFormat(bitmap);
	SDL_FreeSurface(bitmap);
	
	update.x = 0;
	update.y = 0;
	update.w = converted->w;
	update.h = converted->h;
	
	/* Display it for awhile then free it. */
	SDL_BlitSurface(converted, &update, screen, &update);
	SDL_UpdateRect(screen, 0, 0, X, Y);
	while(sdl_input(NULL, NULL) != SDLK_RETURN) /* this could break.. TODO */
		SDL_Delay(100);

	SDL_FreeSurface(converted);
	return 0;
}

void draw_all(SDL_Surface *screen, const struct cell_cluster *cluster, enum DISPLAY_TYPE type) {
	int x, y;

	for(x = 0; x < X; x++) {
		for(y = 0; y < Y; y++) {
			/* What are we going to blanket update the screen with?. */
			switch(type) {
				case DRAW_ENERGY:
					draw_local_energy(screen, cluster, x, y, 0, 0);
					break;
				case DRAW_GENERATION:
					draw_local_generation(screen, cluster, x, y, 0, 0);
					break;
				case DRAW_LIVING:
					draw_local_living(screen, cluster, x, y, 0, 0);
					break;
				default:
					break;
			}
		}
	}
	SDL_UpdateRect(screen, 0, 0, X*PIXELSPEROBJECT, Y*PIXELSPEROBJECT);
}

void draw_local_energy(SDL_Surface *screen, const struct cell_cluster *cluster, int x, int y, char neighbours, char render) {
	int i, xptr, yptr;
	struct cell_proc *proc;

	proc = cluster->cells[x][y];

	display_update(screen, x, y, proc->energy*5, 0, 0, 0);

	if(neighbours) {
		for(i = LEFT; i <= DOWN; i++) {
			get_neighbour_coords(x, y, i, &xptr, &yptr);
			draw_local_energy(screen, cluster, xptr, yptr, 0, 0);
		}
	}

	if(render)
		SDL_UpdateRect(screen, 0, 0, X*PIXELSPEROBJECT, Y*PIXELSPEROBJECT);
}

void draw_local_generation(SDL_Surface *screen, const struct cell_cluster *cluster, int x, int y, char neighbours, char render) {
	int i, xptr, yptr;
	struct cell_proc *proc;

	proc = cluster->cells[x][y];

	/* Attempt to fit more colours in by stepping thru the R,G,B scale, very bad way to do this. */
	if(proc->gen < 256)
		display_update(screen, x, y, 0, proc->gen, 0, 0);
	else if(proc->gen < 512)
		display_update(screen, x, y, 0, 255, proc->gen, 0);
	else if(proc->gen < 768)
		display_update(screen, x, y, proc->gen, 255, 255, 0);
	else
		 printf("color overflow -fix me- ....%d\n", proc->gen);

	if(neighbours) {
		for(i = LEFT; i <= DOWN; i++) {
			get_neighbour_coords(x, y, i, &xptr, &yptr);
			draw_local_generation(screen, cluster, xptr, yptr, 0, 0);
		}
	}

	if(render)
		SDL_UpdateRect(screen, 0, 0, X*PIXELSPEROBJECT, Y*PIXELSPEROBJECT);
}

void draw_local_living(SDL_Surface *screen, const struct cell_cluster *cluster, int x, int y, char neighbours, char render) {
	int i, xptr, yptr;
	struct cell_proc *ptr;

	ptr = cluster->cells[x][y];

	/* Update the current pixel. */
	if(ptr->energy > 0)	
		display_update(screen, x, y, 0, 0, 255, 0);
	else
		display_update(screen, x, y, 0, 0, 0, 0);

	if(neighbours) {
		/* And its neighbours. */
		for(i = LEFT; i <= DOWN; i++) {
			/* Get the direction coords for the neighbour with a cellvm helper call and
                         * recursivly call this function to update each neighbour. */
			get_neighbour_coords(x, y, i, &xptr, &yptr);
			draw_local_living(screen, cluster, xptr, yptr, 0, 0);
		}
	}

	if(render)
		SDL_UpdateRect(screen, 0, 0, X*PIXELSPEROBJECT, Y*PIXELSPEROBJECT);
}

